/*
	Copyright (c) 2004 Cory Nelson

	Permission is hereby granted, free of charge, to any person obtaining
	a copy of this software and associated documentation files (the
	"Software"), to deal in the Software without restriction, including
	without limitation the rights to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to
	permit persons to whom the Software is furnished to do so, subject to
	the following conditions:

	The above copyright notice and this permission notice shall be included
	in all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
	CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include "gamestat.h"
#include "internal.h"

#define MAKEVER(v,s) ((((unsigned short)v)&0xFF)|(((unsigned short)s)<<8))
#define GAMESTAT_VERSION MAKEVER(1,1)

typedef GS_SERVERINFO* (*client_cb)(const char*, unsigned short);
typedef void (*colorfilter_cb)(char*, const char*);
typedef struct {
	client_cb queryserver;
	colorfilter_cb colorfilter;
	unsigned short port;
} GS_TYPE;

unsigned short lasterror=GS_OK;
static unsigned char colorfilter=GS_FALSE;

static const GS_TYPE typelookup[]={
	{queryserver_quakeworld,stripcolor_quakeworld,27500},	// Quake World
	{queryserver_quake2,stripcolor_null,27910},								// Quake 2
	{queryserver_quake3,stripcolor_quake3,27960},			// Quake 3
	{queryserver_quake4,stripcolor_quake3,28004},			// Quake 4
	{queryserver_doom3,stripcolor_null,27666},								// Doom 3
	{queryserver_halflife,stripcolor_null,27015},							// Half-Life
	{queryserver_gamespy,stripcolor_null,7787},								// Unreal Engine 2
	{queryserver_gamespy2,stripcolor_null,0},								// GameSpy Protocol 2
	{queryserver_gamespy2,stripcolor_null,23000},							// BattleField Vietnam
	{queryserver_gamespy2,stripcolor_null,3455},							// Painkiller
	{queryserver_gamespy,stripcolor_null,7787},								// Unreal Tournament 2003
	{queryserver_gamespy,stripcolor_null,7787},								// Unreal Tournament 2004
	{queryserver_gamespy,stripcolor_null,7787},								// Unreal 2 XMP
	{queryserver_sof2,stripcolor_quake3,20100},				// Soldier of Fortune 2
	{queryserver_quake3,stripcolor_quake3,27960},			// Return to Castle Wolfenstein
	{queryserver_quake3,stripcolor_quake3,27960},			// Wolfenstein: Enemy Territory
	{queryserver_halflife,stripcolor_null,27015},							// Counter-Strike
	{queryserver_halflife,stripcolor_null,27015},							// Counter-Strike: Condition Zero
	{queryserver_quake3,stripcolor_quake3,28960},			// Call of Duty
	{queryserver_quake3,stripcolor_quake3,28960},			// Call of Duty 2
	{queryserver_quake3,stripcolor_quake3,29070},			// Star Wars Jedi Knight: Jedi Academy
	{queryserver_moh,stripcolor_quake3,12203},				// Medal of Honor: Allied Assault
	{queryserver_moh,stripcolor_quake3,12203},				// Medal of Honor: Breakthrough
	{queryserver_moh,stripcolor_quake3,12203},				// Medal of Honor: Spearhead
	{queryserver_halflife,stripcolor_null,27015},							// Day of Defeat
	{queryserver_sof,stripcolor_sof,28910},					// Soldier of Fortune
	{queryserver_gamespy,stripcolor_null,0},									// GameSpy Protocol
	{queryserver_gamespy,stripcolor_null,7778},								// Unreal Tournament
	{queryserver_quake3,stripcolor_quake3,28070},			// Star Wars Jedi Knight 2: Jedi Outcast
	{queryserver_gamespy,stripcolor_null,20142},							// Descent 3
	{queryserver_gamespy2,stripcolor_null,1717},							// America's Army
	{queryserver_gamespy,stripcolor_null,23000},							// Battlefield 1942
	{queryserver_gamespy2,stripcolor_null,2301},							// Halo: Combat Evolved
	{queryserver_gamespy,stripcolor_null,7778},								// Postal 2
	{queryserver_quake3,stripcolor_quake3,27960},			// Star Trek: Voyager - Elite Force
	{queryserver_quake3,stripcolor_quake3,29253},			// Star Trek: Voyager - Elite Force 2
	{queryserver_gamespy,stripcolor_null,7791},								// Deus Ex
	{queryserver_gamespy,stripcolor_null,21000},							// IL-2 Sturmovik
	{queryserver_gamespy,stripcolor_null,21000},							// IL-2 Sturmovik: Forgotten Battles
	{queryserver_source,stripcolor_null,0},									// Source Engine
	{queryserver_source,stripcolor_null,27015},								// Half-Life 2
	{queryserver_source,stripcolor_null,27015},								// Counter-Strike: Source
	{queryserver_ase,stripcolor_null,0},										// All-Seeing Eye Protocol
	{queryserver_ase,stripcolor_null,2126},									// Multi Theft Auto
	{queryserver_ase,stripcolor_null,2126},									// Multi Theft Auto: Vice City
	{queryserver_gamespy,stripcolor_null,28910},							// Heretic 2
	{queryserver_gamespy,stripcolor_null,26001},							// IGI-2: Covert Strike
	{queryserver_gamespy,stripcolor_null,27778},							// Gore
	{queryserver_gamespy,stripcolor_null,15425},							// Vietcong
	{queryserver_gamespy2,stripcolor_null,5121},							// Neverwinter Nights
	{queryserver_gamespy,stripcolor_null,25601},							// Serious Sam
	{queryserver_gamespy,stripcolor_null,25601},							// Serious Sam: The Second Encounter
	{queryserver_ase,stripcolor_null,27244},									// Age of Empires II
	{queryserver_ase,stripcolor_null,27244},									// Age of Empires II: The Conquerors
	{queryserver_gamespy,stripcolor_null,27888},							// Aliens vs Predator 2
	{queryserver_ase,stripcolor_null,3123},									// Chaser
	{queryserver_ase,stripcolor_null,27138},									// Chrome
	{queryserver_gamespy,stripcolor_null,27888},							// No One Lives Forever
	{queryserver_gamespy,stripcolor_null,27890},							// No One Lives Forever 2
	{queryserver_gamespy,stripcolor_null,27888},							// Shogo
	{queryserver_ase,stripcolor_null,23196},									// Soldat
	{queryserver_gamespy,stripcolor_null,4711},								// Codename Eagle
	{queryserver_ase,stripcolor_null,7900},									// Devastation
	{queryserver_gamespy,stripcolor_null,8911},								// Giants: Citizen Kabuto
	{queryserver_gamespy,stripcolor_null,28672},							// Global Operations
	{queryserver_gamespy,stripcolor_null,4022},								// Nerf Arena Blast
	{queryserver_ase,stripcolor_null,27777},									// Purge Jihad
	{queryserver_gamespy,stripcolor_null,17500},							// RalliSport Challenge
	{queryserver_gamespy,stripcolor_null,16666},							// Rally Masters
	{queryserver_gamespy,stripcolor_null,25300},							// Command and Conquer: Renegade
	{queryserver_gamespy,stripcolor_null,7778},								// Rune
	{queryserver_gamespy,stripcolor_null,22449},							// Sin
	{queryserver_gamespy,stripcolor_null,7778},								// Tactical Ops
	{queryserver_ase,stripcolor_null,1258},									// Universal Combat
	{queryserver_gamespy,stripcolor_null,7778},								// Unreal
	{queryserver_gamespy,stripcolor_null,7778},								// Wheel of Time
	{queryserver_ase,stripcolor_null,28138},									// Xpand Rally
	{queryserver_gamespy,stripcolor_null,25300},							// Deadly Dozen: Pacific Theater
	{queryserver_gamespy,stripcolor_null,32140},							// Dirt Track Racing II
	{queryserver_gamespy,stripcolor_null,27046},							// Drakan: Order of the Flame
	{queryserver_gamespy,stripcolor_null,3297},								// F1 2002
	{queryserver_halflife,stripcolor_null,27015},							// Gunman Chronicles
	{queryserver_gamespy,stripcolor_null,3505},								// Iron Storm
	{queryserver_gamespy,stripcolor_null,6550},								// James Bond: Nightfire
	{queryserver_gamespy,stripcolor_null,31500},							// Kingpin
	{queryserver_gamespy,stripcolor_null,61220},							// Need for Speed: Hot Pursuit 2
	{queryserver_gamespy2,stripcolor_null,2235},							// Operation Flashpoint
	{queryserver_gamespy,stripcolor_null,25252},							// Redline
	{queryserver_gamespy,stripcolor_null,12286},							// Terminus
	{queryserver_gamespy,stripcolor_null,12880},							// Turok 2
	{queryserver_gamespy,stripcolor_null,27888},							// Tron 2.0
	{queryserver_tribes,stripcolor_null,28001},								// Starsiege: Tribes
	{queryserver_tribes2,stripcolor_null,28000},							// Tribes 2
	{queryserver_gamespy,stripcolor_null,6500},								// Tony Hawk's Pro Skater 3
	{queryserver_gamespy,stripcolor_null,6500},								// Tony Hawk's Pro Skater 4
	{queryserver_gamespy,stripcolor_null,16700},							// V8 Supercar Challenge
	{queryserver_gamespy,stripcolor_null,57778},							// Team Factor
	{queryserver_gamespy,stripcolor_null,2346},								// Rainbow Six
	{queryserver_gamespy,stripcolor_null,2346},								// Rainbow Six: Rogue Spear
	{queryserver_gamespy,stripcolor_null,25601},							// Nitro Family
	{queryserver_ase,stripcolor_null,49124},									// Far Cry
	{queryserver_gamespy,stripcolor_null,6501},								// Rise of Nations
	{queryserver_gamespy,stripcolor_null,27888},							// Contact J.A.C.K.
	{queryserver_gamespy,stripcolor_null,6500},								// Homeworld 2
	{queryserver_quake3,stripcolor_quake3,27992},			// Daikatana
	{queryserver_savage,stripcolor_null,11235},								// Savage
	{queryserver_ghostrecon,stripcolor_null,2348},							// Ghost Recon
	{queryserver_gamespy2,stripcolor_null,7649},							// Breed
	{queryserver_ase,stripcolor_null,12326},									// Medal of Honor: Pacific Assault
	{queryserver_gamespy,stripcolor_null,2303},								// Operation Flashpoint: Resistance
	{queryserver_ghostrecon,stripcolor_null,2348},							// Sum of All Fears
	{queryserver_gamespy,stripcolor_null,22101},							// Star Trek: Bridge Commander
	{queryserver_quake3,stripcolor_quake3,28960},			// Call of Duty: United Offensive
	{queryserver_gamespy2,stripcolor_null,3658},							// Star Wars: Battlefront
	{queryserver_gamespy2,stripcolor_null,7778},							// Tribes: Vengeance
	{queryserver_gamespy2,stripcolor_null,5153}								// Tony Hawk's Underground 2
};
static const size_t maxtype=sizeof(typelookup)/sizeof(GS_TYPE);

unsigned char GSInit(unsigned char flags) {
#if defined(WIN32) && !defined(_WINDLL)
	if(flags&GS_INIT_WINSOCK) {
		WSADATA data;
		if(WSAStartup(MAKEWORD(2,2), &data)) {
			lasterror=GS_ERROR_WINSOCK;
			return GS_FALSE;
		}
	}
#endif

	lasterror=GS_OK;
	return GS_TRUE;
}

unsigned char GSCleanup(unsigned char flags) {
#if defined(WIN32) && !defined(_WINDLL)
	if(flags&GS_CLEANUP_WINSOCK) {
		if(WSACleanup()) {
			lasterror=GS_ERROR_WINSOCK;
			return GS_FALSE;
		}
	}
#endif

	lasterror=GS_OK;
	return GS_TRUE;
}

void GSSetOptBool(unsigned short option, unsigned char value) {
	switch(option) {
		case GS_OPTION_COLORFILTER:
			colorfilter=value;
			break;
	}
}

void GSSetOptInt(unsigned short option, int value) {
	switch(option) {
		case GS_OPTION_TIMEOUT:
			socktimeout.tv_usec=value%1000;
			value-=socktimeout.tv_usec;

			socktimeout.tv_sec=value/1000;
			socktimeout.tv_usec*=1000;
			break;
	}
}

unsigned char GSGetOptBool(unsigned short option) {
	switch(option) {
		case GS_OPTION_COLORFILTER:
			return colorfilter;
		default:
			return GS_FALSE;
	}
}

int GSGetOptInt(unsigned short option) {
	switch(option) {
		case GS_OPTION_TIMEOUT:
			return (socktimeout.tv_sec*1000)+(socktimeout.tv_usec/1000);
		default:
			return 0;
	}
}

GS_SERVERINFO *GSQueryServer(unsigned short type, const char *host, unsigned short port) {
	if(!host) {
		lasterror=GS_ERROR_UNKNOWNHOST;
		return NULL;
	}
	if(type>=maxtype) {
		lasterror=GS_ERROR_UNKNOWNTYPE;
		return NULL;
	}
	else if(typelookup[type].port==0 && port==0) {
		lasterror=GS_ERROR_UNKNOWNPORT;
		return NULL;
	}
	else {
		GS_SERVERINFO *info=typelookup[type].queryserver(host,((port>0)?port:typelookup[type].port));
		if(info) {
			if(colorfilter==GS_TRUE && typelookup[type].colorfilter) {
				GS_PLAYERINFO *player;

				if(info->host) typelookup[type].colorfilter(info->host, info->host);

				for(player=info->players; player!=NULL; player=player->next)
					if(player->name) typelookup[type].colorfilter(player->name, player->name);
			}
			return info;
		}
		else return NULL;
	}
}

void GSFreeServerInfo(GS_SERVERINFO *info) {
	if(info) {
		GS_PLAYERINFO *player, *nextplayer=NULL;
		GS_PROPERTY *property, *nextproperty=NULL;

		if(info->host) free(info->host);
		if(info->mod) free(info->mod);
		if(info->map) free(info->map);

		for(player=info->players; player!=NULL; player=nextplayer) {
			if(player->name) free(player->name);

			nextplayer=player->next;
			free(player);
		}

		for(property=info->properties; property!=NULL; property=nextproperty) {
			if(property->key) free(property->key);
			if(property->value) free(property->value);

			nextproperty=property->next;
			free(property);
		}

		free(info);
	}
}

GS_SERVER *GSQueryMaster(unsigned short type, const char *host, unsigned short port, unsigned short filter) {
	switch(type) {
		case GS_MASTER_CS:
			return querymaster_steam((host?host:"steam1.steampowered.com"), ((port>0)?port:27010), "cstrike", filter);
		case GS_MASTER_CSCZ:
			return querymaster_steam((host?host:"steam1.steampowered.com"), ((port>0)?port:27010), "czero", filter);
		case GS_MASTER_CSS:
			return querymaster_steam((host?host:"steam1.steampowered.com"), ((port>0)?port:27011), "cstrike", filter);
		case GS_MASTER_DOD:
			return querymaster_steam((host?host:"steam1.steampowered.com"), ((port>0)?port:27010), "dod", filter);
		case GS_MASTER_DOOM3:
			return querymaster_doom3((host?host:"idnet.ua-corp.com"), ((port>0)?port:27650), filter);
		case GS_MASTER_HALFLIFE:
			return querymaster_steam((host?host:"steam1.steampowered.com"), ((port>0)?port:27010), "dmc", filter);
		case GS_MASTER_QUAKE2:
			return querymaster_quake2((host?host:"satan.idsoftware.com"), ((port>0)?port:27900));
		case GS_MASTER_QUAKE3:
			return querymaster_quake3((host?host:"monster.idsoftware.com"), ((port>0)?port:27950), 68, filter);
		case GS_MASTER_RTCW:
			return querymaster_quake3((host?host:"wolf.idsoftware.com"), ((port>0)?port:27950), 60, filter);
		case GS_MASTER_SOF2:
			return querymaster_sof2((host?host:"master.sof2.ravensoft.com"), ((port>0)?port:20110), 2004, filter);
		case GS_MASTER_STVEF:
			return querymaster_stvef((host?host:"master.stef1.ravensoft.com"), ((port>0)?port:27953), 24, filter);
		case GS_MASTER_STVEF2:
			return querymaster_stvef((host?host:"ef2master.activision.com"), ((port>0)?port:29250), 66, filter);
		case GS_MASTER_SWJK2:
			return querymaster_swjk2((host?host:"masterjk2.ravensoft.com"), ((port>0)?port:28060), 16, filter);
		case GS_MASTER_SWJKJA:
			return querymaster_swjk2((host?host:"masterjk3.ravensoft.com"), ((port>0)?port:29060), 26, filter);
		case GS_MASTER_WOLFET:
			return querymaster_quake3((host?host:"etmaster.idsoftware.com"), ((port>0)?port:27950), 83, filter);
		default:
			lasterror=GS_ERROR_UNKNOWNTYPE;
			return NULL;
	}
}

void GSFreeServers(GS_SERVER *servers) {
	GS_SERVER *server, *next;

	for(server=servers;server!=NULL;server=next) {
		if(server->host) free(server->host);
		
		next=server->next;
		free(server);
	}
}

char *GSStripColor(unsigned short type, char *out, const char *in) {
	if(!out || !in) {
		lasterror=GS_ERROR_INVALIDPARAMETER;
		return NULL;
	}
	else if(type>=maxtype) {
		lasterror=GS_ERROR_UNKNOWNTYPE;
		return NULL;
	}
	else if(typelookup[type].colorfilter==NULL && out!=in) {
		strcpy(out, in);
		return out;
	}
	else {
		typelookup[type].colorfilter(out, in);
		return out;
	}
}

unsigned short GSLastError(void) {
	return lasterror;
}

const char *GSErrorString(unsigned short err) {
	static const char *errorlookup[]={
		"Everything went okay",										// GS_OK
		"An internal Windows Sockets error occured",			// GS_ERROR_WINSOCK
		"Unknown type",												// GS_ERROR_UNKNOWNTYPE
		"Unable to resolve host or invalid IP",				// GS_ERROR_UNKNOWNHOST
		"Unable to connect to host",								// GS_ERROR_CONNECT
		"Connection timed out, host may be unreachable",	// GS_ERROR_TIMEOUT
		"Unknown or currupt response recieved",				// GS_ERROR_CORRUPT
		"Could not determine the server port",					// GS_ERROR_UNKNOWNPORT
		"Function not yet implemented",							// GS_ERROR_NOTIMPLEMENTED
		"Invalid parameter"											// GS_ERROR_INVALIDPARAMETER
	};
	size_t maxerror=sizeof(errorlookup)/sizeof(const char*);

	if(err>maxerror) return "Unknown error code";
	else return errorlookup[err];
}

const char *GSLastErrorString(void) {
	return GSErrorString(lasterror);
}

unsigned short GSVersion(void) {
	return GAMESTAT_VERSION;
}

#ifdef _DEBUG
void dbg_dumpbuf(const char *file, const void *buf, size_t size) {
	FILE *fp=fopen(file, "wb");
	fwrite(buf, size, 1, fp);
	fclose(fp);
}
#endif

#ifdef _WINDLL
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpReserved) {
	switch(fdwReason) {
		case DLL_PROCESS_ATTACH: {
			WSADATA data;
			if(WSAStartup(MAKEWORD(2,2), &data)) return FALSE;
		} break;
		case DLL_PROCESS_DETACH:
			WSACleanup();
			break;
	}
	return TRUE;
}
#endif
